#include "xict_cpp/bridge/udp_socket.h"

namespace bridge {

    /** server **/
    UdpSocket::UdpSocket(boost::asio::io_service& io_service, short port,
                         bool auto_receive)
        : ioService(io_service), socket_(io_service),
          deadline_(io_service),   // no use for server
          messageCallback_(DefaultUdpMessageCallback),
          writeCompleteCallback_(DefaultUdpWriteCompleteCallback) {
        socket_.open(Udp::v4());
        socket_.set_option(
            boost::asio::socket_base::reuse_address(true));   // 设置端口复用
        socket_.bind(Udp::endpoint(Udp::v4(), port));         // 绑定端口

        if (auto_receive)
            DoAsyncRead();
    }

    /** client **/
    UdpSocket::UdpSocket(boost::asio::io_service& io_service,
                         const std::string& host, short port, bool auto_receive)
        : ioService(io_service), socket_(io_service), deadline_(io_service),
          messageCallback_(DefaultUdpMessageCallback),
          writeCompleteCallback_(DefaultUdpWriteCompleteCallback) {
        Udp::resolver resolver(ioService);
        Udp::resolver::query query(Udp::v4(), host, std::to_string(port));
        Udp::resolver::iterator iter = resolver.resolve(query);
        endpoint_                    = *iter;
        socket_.open(Udp::v4());

        deadline_.expires_at(boost::posix_time::pos_infin);
        CheckDeadline();
        Send("hello ,from new udp client");
        if (auto_receive)
            DoAsyncRead();
        auto t = std::thread([&]() { io_service.run(); });
        t.detach();
    }

    UdpSocket::~UdpSocket() {
        socket_.close();
    }

    void UdpSocket::DoAsyncRead() {
        AsyncReceive();
    }

    void UdpSocket::CheckDeadline() {
        if (deadline_.expires_at() <= deadline_timer::traits_type::now()) {
            socket_.cancel();
            deadline_.expires_at(boost::posix_time::pos_infin);
        }
        deadline_.async_wait(std::bind(&UdpSocket::CheckDeadline, this));
    }

    void UdpSocket::AsyncSend(const void* data, size_t len) {
        socket_.async_send_to(boost::asio::buffer(data, len), endpoint_,
                              [this](auto&& PH1, auto&& PH2) {
                                  OnWriteComplete(
                                      std::forward<decltype(PH1)>(PH1),
                                      std::forward<decltype(PH2)>(PH2));
                              });
    }

    void UdpSocket::AsyncSend(const std::string& content) {
        socket_.async_send_to(
            boost::asio::buffer(content.data(), content.size()), endpoint_,
            [this](auto&& PH1, auto&& PH2) {
                OnWriteComplete(std::forward<decltype(PH1)>(PH1),
                                std::forward<decltype(PH2)>(PH2));
            });
    }

    bool UdpSocket::Send(const uint8_t* data, size_t len) {
        try {
            socket_.send_to(boost::asio::buffer(data, len), endpoint_);
        } catch (const std::exception& e) {
            std::cerr << e.what() << std::endl;
            return false;
        }
        return true;
    }

    bool UdpSocket::Send(const std::string& content) {
        try {
            socket_.send_to(boost::asio::buffer(content.data(), content.size()),
                            endpoint_);
        } catch (const std::exception& e) {
            std::cerr << e.what() << std::endl;
            return false;
        }
        return true;
    }

    void UdpSocket::AsyncReceive() {
        socket_.async_receive_from(
            boost::asio::buffer(recvBuffer_, BUF_SIZE), endpoint_,
            std::bind(&UdpSocket::OnMessage, this, std::placeholders::_1,
                      std::placeholders::_2));
    }

    size_t UdpSocket::Receive(void* data) {
        return socket_.receive_from(boost::asio::buffer(data, BUF_SIZE),
                                    endpoint_);
    }

    void UdpSocket::OnMessage(const boost::system::error_code& error,
                              size_t len) {
        if (!error) {
            auto t = std::thread([=]() { messageCallback_(recvBuffer_, len); });
            t.detach();

            DoAsyncRead();
        }
    }

    void UdpSocket::OnWriteComplete(const boost::system::error_code& error,
                                    size_t len) {
        if (!error) {
            std::cout << "write " << len << "bytes finish" << std::endl;
            writeCompleteCallback_(len);
        }
    }

}   // namespace bridge
